using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Player : AbstractCharacter {
	
	public enum PlayerState {
		Idle,
		Walking,
		Digging,
		Climbing
	}
	
	public bool godMode = false;
	
	public float climbSpeed = 1f;
	public float fogOfWarRange = 0.6f;
	public float supportTime = 40f;
	public float digInterval = 0.1f;
	public float climbDelay = 0.1f;
	public int bombCount = 3;
	public int supportCount = 6;
		
	public float blockDamage = 25f;
	public GameEngine engine;
	public Camera cameraGame;
	public Vector3 cameraOffset = new Vector3(0,1f,0);
		
	[HideInInspector]
	public bool locked = false;
	
	public int bombs { get; private set; }
	public int supports { get; private set; }
	public int gold { get; private set; }
	public bool IsClimbing { get { return _isClimbing && Time.time-_climbStartTime>=climbDelay; }}
	
	public Transform PrefabBomb;
	public Transform PrefabLadder;
	
	private PlayerState _state;
	private PlayerState _lastState;
	private int _facingDirection;
	private Rect _bounds;
	private float _digTime;
	private float _climbStartTime;
	private Animation _animation;
	
	private bool _isClimbing;
	private bool _isDigging;
	private int _dx;
	private int _dy;
	private List<Transform> _ladders;
	private List<Bomb> _bombs;
	private List<AbstractElement> _contacts;
	
	public PlayerState state {
		get {
			return _state;	
		} set {
			if(value==_state) return;
			
			_lastState = _state;
			_state = value;
			
			switch(_state){
			case PlayerState.Idle :
				_animation.CrossFade("PlayerIdle",0.3f);
				break;
			case PlayerState.Walking :
				_animation.CrossFade("PlayerWalking",0.3f);
				break;
			case PlayerState.Digging :
				_animation.CrossFade("PlayerDigging",0.3f);
				break;
			case PlayerState.Climbing :
				_animation.CrossFade("PlayerClimbing",0.3f);
				break;
			}
		}
	}
	
	public void SetBounds(Rect r){
		r.x *= 0.6f;
		r.y *= 0.7f;
		r.width *= 0.6f;
		r.height *= 0.7f;
		_bounds = r;	
	}
	public void Reset(bool dead = false){
		if(dead){
			foreach(Bomb b in _bombs){
				if(b==null) continue;
				b.Clear();	
				Destroy(b.gameObject);
			}
			foreach(Transform l in _ladders){
				Destroy(l.gameObject);	
			}
			
			_ladders = new List<Transform>();
			_bombs = new List<Bomb>();
			_contacts = new List<AbstractElement>();
		}
		
		IsDead = false;
		bombs = bombCount;
		supports = supportCount;
		gold = 0;
		gameObject.SetActiveRecursively(true);
		
		state = PlayerState.Idle;
	}
	public void CollectGold(int amount){
		if(amount<=0) return;
		
		gold += amount;
	}

	protected void Awake(){
		_bombs = new List<Bomb>();
		_ladders = new List<Transform>();
		_contacts = new List<AbstractElement>();
		_animation = GetComponentInChildren<Animation>();
		
		_isClimbing = false;
		_facingDirection = 1;
		_digTime = Time.time;
		_climbStartTime = Time.time;
		
		Reset();
	}
	
	protected void Update(){
		if(IsDead || locked) return;
		
		// get inputs
		_isDigging = Input.GetKey(KeyCode.Space);
		_dy = (int)Input.GetAxisRaw("Vertical");
		_dx = _dy!=0 ? 0 : (int)Input.GetAxisRaw("Horizontal");
		if(_dx!=0) _facingDirection = _dx;
		
		transform.localScale = new Vector3(0.16f,0.16f,0.16f*_facingDirection);
		
		if(Input.GetKeyUp(KeyCode.E) && bombs>0){
			Transform bomb = (Transform)Instantiate(PrefabBomb,transform.position, Quaternion.identity);
			Bomb b = bomb.GetComponent<Bomb>();
			b.Setup(engine);
			_bombs.Add(b);
			bombs--;
		}
		if(Input.GetKeyUp(KeyCode.Q) && supports>0){
			Vector2 gp = engine.GetGridPosition(transform.position);
			Block b = engine.GetBlockAt((int)gp.x,(int)gp.y);
			
			if(b!=null && b.IsDestroyed && !b.IsLadder){
				supports--;
				b.AddLadder();
				
				Transform lt = Instantiate(PrefabLadder,b.transform.position,Quaternion.Euler(0,90,0)) as Transform;
				_ladders.Add(lt);
			}
		}
	}
	
	protected void FixedUpdate(){
		if(IsDead) return;
		
		Vector3 p = transform.position;
		
		float vx = (speed*_dx)*Time.fixedDeltaTime;
		float vy = gravity*Time.fixedDeltaTime;
		
		Rect cb = _bounds;
		cb.x += p.x;
		cb.y += p.y;
		
		// check for contacts
		List<AbstractElement> persistentContacts = new List<AbstractElement>();
		List<IQuadObject> colliders = engine.Search(cb);
		foreach(AbstractElement e in colliders){
			if(e.IsTrigger && !e.IsDestroyed){
				persistentContacts.Add(e);
				if(!_contacts.Contains(e)){
					e.OnContactAdded();
					_contacts.Add(e);
				}
			}
		}
		
		int i = _contacts.Count-1;
		for(; i>=0; --i){
			AbstractElement e = _contacts[i];
			if(!persistentContacts.Contains(e)){
				e.OnContactRemoved();
				_contacts.Remove(e);
			}
		}
		
		// check X collisions
		Rect nb = new Rect(_bounds.x,_bounds.y*0.5f,_bounds.width,_bounds.height*0.5f);
		nb.x += p.x+vx;
		nb.y += p.y;
		
		List<IQuadObject> collidersX = engine.Search(nb);
		AbstractElement collideX = null;
		foreach(AbstractElement e in collidersX){
			if(e!=null && !e.IsDestroyed && !e.IsTrigger && UtilsRect.intersects(nb,e.worldBounds)){
				collideX = e;
				break;
			}
		}
		
		
		if(collideX!=null){
			float cx = collideX.transform.position.x;
			
			if(cx<p.x) vx += collideX.worldBounds.xMax-cb.xMin;
			else if(cx>p.x) vx += collideX.worldBounds.xMin-cb.xMax;
		
			// can climb?
			Block ct = collideX as Block;
			if(ct!=null){
				int offset = _dx<0 ? 1 : -1;
				Block tb = engine.GetBlockAt(ct.x+offset,ct.y+1);
				Block tb2 = engine.GetBlockAt(ct.x,ct.y+1);
				
				if((!_isDigging || IsClimbing) && ((_dx<0 && cx<p.x) || (_dx>0 && cx>p.x))){
					if(IsClimbing) vy = climbSpeed*Time.fixedDeltaTime;
					
					if((tb==null || tb.IsDestroyed) && (tb2==null || tb2.IsDestroyed)){
						if(!_isClimbing) _climbStartTime = Time.time;
						_isClimbing = true;
					} else if(IsClimbing){
						vy = cb.yMax+vy<tb.worldBounds.yMin ? vy : 0;	
					}
				} else _isClimbing = false;
			} else _isClimbing = false;
		} else _isClimbing = false;
		
		p.x += vx;
		
		// check Y collisions
		nb = new Rect(_bounds.x*0.5f,_bounds.y,_bounds.width*0.5f,_bounds.height);
		nb.x += p.x;
		nb.y += p.y+vy;
		
		List<IQuadObject> collidersY = engine.Search(nb);
		AbstractElement collideY = null;
		foreach(AbstractElement e in collidersY){
			if(e!=null && !e.IsTrigger && UtilsRect.intersects(nb,e.worldBounds)){
				Block b = e as Block;
				// give ladders priority
				bool bl = (b!=null && b.IsLadder);
				if(!e.IsDestroyed || bl){
					if(collideY==null || bl) collideY = e;
					if(bl) break;
				}
			}
		}
		
		bool onLadder = false;
		if(collideY!=null && !IsClimbing){
			// check for ladders
			
			if(collideY is Block && (collideY as Block).IsLadder){
				onLadder = true;
				Vector2 ppp = engine.GetGridPosition(p);
				Block bp = engine.GetBlockAt((int)ppp.x,(int)ppp.y);
				Block b = (collideY as Block);
				Block bu = engine.GetBlockAt(b.x,b.y+1);
				Block bpu = bp!=null ? engine.GetBlockAt(bp.x,bp.y+1) : null;
				Block bd = engine.GetBlockAt(b.x,b.y-1);
				
				vy = (climbSpeed*Time.deltaTime)*_dy;
				if(bpu!=null && !bpu.IsDestroyed && (p.y+vy)-_bounds.yMin>=bpu.worldBounds.yMin){
					p.y = bp!=null ? bp.worldBounds.yMax+_bounds.yMin : bpu.worldBounds.yMin+_bounds.yMin;
				} else if((p.y+vy)+_bounds.yMin>collideY.worldBounds.yMax && (bu==null || !bu.IsLadder)){
					p.y = collideY.worldBounds.yMax-_bounds.yMin;
				} else if((p.y+vy)+_bounds.yMin<collideY.worldBounds.yMin && (bd!=null && !bd.IsDestroyed)){
					p.y = collideY.worldBounds.yMin-_bounds.yMin;
				} else p.y += vy;
			} else {
				p.y = collideY.worldBounds.yMax-_bounds.yMin;
			}
		} else p.y += vy;
		
		// update player state
		if(_isDigging) state = PlayerState.Digging;
		else if(onLadder && _dy!=0) state = PlayerState.Climbing;
		else if(!onLadder && _dx!=0) state = PlayerState.Walking;
		else state = PlayerState.Idle;
		
		// update player position
		transform.position = p;
		
		if(!locked){
			Vector3 cp = p+cameraOffset;
			if(cp.y<engine.worldBounds.yMin+1.2f) cp.y = engine.worldBounds.yMin+1.2f;
			if(cp.x<engine.worldBounds.xMin+2.1f) cp.x = engine.worldBounds.xMin+2.1f;
			if(cp.x>engine.worldBounds.xMax-2.1f) cp.x = engine.worldBounds.xMax-2.1f;
			cameraGame.transform.position = cp;	
		}
		
		// attack blocks
		if(_isDigging && Time.time-_digTime>=digInterval){
			Rect ab = _bounds;
			ab.x *= 0.5f;
			ab.y *= 0.5f;
			ab.height *= 0.5f;
			ab.width *= 0.5f;
			
			UtilsDebug.DrawRect(ab,Color.red);
			
			float dx = _dy==0 ? _facingDirection : 0;
			ab.x += p.x+(_bounds.width*dx);
			ab.y += p.y+(_bounds.height*_dy);
			
			List<IQuadObject> collidersDig = engine.Search(ab);
			foreach(AbstractElement e in collidersDig){
				Block b = e as Block;
				if(b!=null && !b.IsDestroyed) b.ApplyDamage(blockDamage);
			}
			
			_digTime = Time.time;
		}
	}
	
}
